﻿using System;
using System.Collections.Generic;
using System.Text;

namespace SharpSoft.Geometries
{
    /// <summary>
    /// 使用三个顶点定义的平面三角形
    /// </summary>
    public struct Trigon2D
    {
        public Trigon2D(Point2D p1, Point2D p2, Point2D p3)
        {
            this.P1 = p1;
            this.P2 = p2;
            this.P3 = p3;
        }

        public Point2D P1 { get; set; }
        public Point2D P2 { get; set; }
        public Point2D P3 { get; set; }

        /// <summary>
        /// 获取指定顶点的夹角,1/2/3分别代表P1/P2/P3.
        /// </summary>
        /// <param name="pindex">指定获取哪个顶点的夹角</param>
        /// <returns></returns>
        public Radian GetAngle(int pindex)
        {
            double a = 0d, b = 0d, c = 0d;
            if (pindex == 1)
            {
                a = Line2D.GetLength(this.P1, this.P2);
                b = Line2D.GetLength(this.P3, this.P1);
                c = Line2D.GetLength(this.P2, this.P3);
            }
            else if (pindex == 2)
            {
                a = Line2D.GetLength(this.P1, this.P2);
                b = Line2D.GetLength(this.P2, this.P3);
                c = Line2D.GetLength(this.P1, this.P3);
            }
            else if (pindex == 3)
            {
                a = Line2D.GetLength(this.P3, this.P1);
                b = Line2D.GetLength(this.P2, this.P3);
                c = Line2D.GetLength(this.P1, this.P2);
            }
            else
            {
                throw new Exception("无效的顶点。");
            }

            ///计算c边的对角
            var cos_C = (a.Pow() + b.Pow() - c.Pow()) / (2 * a * b);
            return new Radian(Math.Acos(cos_C));
        }
        /// <summary>
        /// (海伦公式)计算三角形面积
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// 海伦公式：
        /// S=√[p(p-a)(p-b)(p-c)] 
        /// p=(a+b+c)/2
        /// </remarks>
        public double GetArea()
        {
            var a = Line2D.GetLength(this.P1, this.P2);
            var b = Line2D.GetLength(this.P2, this.P3);
            var c = Line2D.GetLength(this.P1, this.P3);

            var p = (a + b + c) / 2;
            return Math.Sqrt(p * (p - a) * (p - b) * (p - c));
        }
        ///// <summary>
        ///// 计算指定三个顶点形成的三角形的面积
        ///// <see cref="http://geomalgorithms.com/Archive/algorithm_0101/algorithm_0101.htm"/>
        ///// </summary>
        ///// <returns>返回的面积可能为负数，可以用来确定三角形的方向</returns>
        //public static double GetArea(Point2D a, Point2D b, Point2D c)
        //{
        //    return ((b.X - a.X) * (c.Y - a.Y) - (c.X - a.X) * (b.Y - a.Y));
        //}

        /// <summary>
        /// 计算测试点是否在三角形内部
        /// <see cref="https://www.gamedev.net/forums/topic/295943-is-this-a-better-point-in-triangle-test-2d/"/>
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <param name="v3"></param>
        /// <param name="test"></param>
        /// <returns></returns>
        /// <remarks>
        /// 顺时针／逆时针判定法。  该方法要求点的顺序是顺时针或逆时针的，如果是顺时针的点，沿着3条边走，如果目标点P在三角形内，那么P始终在边的右侧。
        /// 同理，如果是逆时针的话，目标点p应该始终在边的左侧。 
        /// 例：逆时针的三个点abc，判断abXap , bcXbp , caXcp,如果这三个向量叉积的Z值都同向，并且都为负的话（左手系），说明p点在三角形内部。
        /// （Unity使用的是右手坐标系，也是右手法则，逆时针旋转，大拇指向旋转的正方向）  
        /// </remarks>
        public static Boolean IsInsideTrigon(Point2D A, Point2D B, Point2D C, Point2D P)
        {
            var v0 = C - A;
            var v1 = B - A;
            var v2 = P - A;

            var dot00 =  v0.DotProduct(v0);
            var dot01 = v0.DotProduct(v1);
            var dot02 = v0.DotProduct(v2);
            var dot11 = v1.DotProduct(v1);
            var dot12 = v1.DotProduct(v2);

            var inverDeno = 1 / (dot00 * dot11 - dot01 * dot01);

            var u = (dot11 * dot02 - dot01 * dot12) * inverDeno;
            if (u < 0 || u > 1) // if u out of range, return directly
            {
                return false;
            }

            var v = (dot00 * dot12 - dot01 * dot02) * inverDeno;
            if (v < 0 || v > 1) // if v out of range, return directly
            {
                return false;
            }

            return u + v <= 1;

        }
        /// <summary>
        /// 获取指定顶点到其对边的三角形高
        /// </summary>
        /// <param name="pindex"></param>
        /// <returns></returns>
        /// <remarks>
        /// 三角形面积公式：面积=底X高/2
        /// </remarks>
        public double GetHeight(int pindex)
        {
            var d = 0d;
            if (pindex == 1)
            {
                d = Line2D.GetLength(this.P2, this.P3);
            }
            else if (pindex == 2)
            {
                d = Line2D.GetLength(this.P1, this.P3);
            }
            else if (pindex == 3)
            {
                d = Line2D.GetLength(this.P1, this.P2);
            }
            else
            {
                throw new Exception("无效的顶点。");
            }
            var area = GetArea();
            return area * 2 / d;

        }
    }
}
